#include "stdafx.h"
#include "ParticleSystem.h"

#include "Random.h"
#include "LoadTexture.h"

#include "InputLayouts.h"

using namespace mgfx;
using namespace mutil;

void ParticleSystemDesc::initDefault()
{
	NumParticles = 30;
	WrapAroundTimeMs = 5;

	MinInitialSpeed = 10;
	MaxInitialSpeed = 15;

	MinInitialSize = 5;
	MaxInitialSize = 10;

	MinLifeTimeMs = 4;
	MaxLifeTimeMs = 8;

	MaxInitialPositionDeviation = 5;

	RestrictedMoveDir = true;
	InitialMoveDir = XMFLOAT3(0,1,0);

	Acceleration = XMFLOAT3(0.0f, 0, 0);
	SizeChangeSpeed = 10.1f;
	
	Color = XMCOLOR(1,0,1,1);

	Texture = loadTextureFromFile("DeferredParticle.dds");
}

static void initIB(short * ib, uint numParticles)
{
	//012 023
	for (uint i = 0; i < numParticles; i++)
	{
		ib[i*6 + 0] = i*4 + 0;
		ib[i*6 + 1] = i*4 + 1;
		ib[i*6 + 2] = i*4 + 2;

		ib[i*6 + 3] = i*4 + 0;
		ib[i*6 + 4] = i*4 + 2;
		ib[i*6 + 5] = i*4 + 3;
	}
}

void ParticleSystem::deinit()
{
	delete [] mParticles;
	delete [] Vertices;
	delete [] Indices;
}

void ParticleSystem::init( const ParticleSystemDesc & desc )
{
	assert(Desc.NumParticles < 32767);
	Desc = desc;

	mParticles = new Particle[desc.NumParticles];
	memset(mParticles, 0, desc.NumParticles * sizeof(Particle));

	Vertices = new VertexParticle[desc.NumParticles * 4];
	Indices = new short[desc.NumParticles * 6];

	mPrevFreeIndex = desc.NumParticles - 1;
	mNumToSpawnLeft = 0;

	NumParticlesActive = 0;
	mCurrentTime = 0;

	Type = PARTICLE_SYSTEM;
	//spawnParticles(10);

	maxBBoxInitialized = false;
}

void ParticleSystem::update( float dt )
{
	mCurrentTime += dt;

	float numToSpawn = Desc.NumParticles * dt / Desc.WrapAroundTimeMs + mNumToSpawnLeft;
	uint numToSpawnInt = (uint)floorf(numToSpawn);
	spawnParticles(numToSpawnInt);
	mNumToSpawnLeft = numToSpawn - numToSpawnInt;

	XMVECTOR acc = XMLoadFloat3(&Desc.Acceleration);
	XMVECTOR dtVec = XMVectorReplicate(dt);

	uint currVertex = 0;
	uint counter = 0;

	bool bBoxInitialized = false;
	mmath::BoundingBox bBox = CurrentBoundingBox;

	for (uint i = 0; i < Desc.NumParticles; i++)
	{
		Particle & p = mParticles[i];

		if(p.SpawnTime + p.LifeTimeMs > mCurrentTime)
		{
			XMVECTOR vel = XMLoadFloat3(&p.Velocity);
			vel = XMVectorMultiplyAdd(acc, dtVec, vel);

			XMVECTOR pos = XMLoadFloat3(&p.Position);
			pos = XMVectorMultiplyAdd(vel, dtVec, pos);

			XMStoreFloat3(&p.Velocity, vel);
			XMStoreFloat3(&p.Position, pos);

			p.Size += Desc.SizeChangeSpeed * dt;

			addParticleToVB(p, currVertex);
			currVertex += 4;

			if(!bBoxInitialized)
			{
				bBox.initWithSphere(pos, p.Size * 0.5f * 1.2f); // should be sqrt(2)
				bBoxInitialized = true;
			}
			else
			{
				bBox.growBySphere(pos, p.Size * 0.5f * 1.2f);
			}

			counter++;
		}
	}
	NumParticlesActive = counter;

	CurrentBoundingBox = bBox;

	if(bBox.isNonZero())
	{
		if(!maxBBoxInitialized)
		{
			MaxBoundingBox = bBox;
			maxBBoxInitialized = true;
		}
		else
		{
			MaxBoundingBox = mmath::BoundingBox::maximum(MaxBoundingBox, bBox);
		}
	}
}

void ParticleSystem::addParticleToVB( const Particle & p, uint firstVertexPosInBuffer)
{
	uint i = firstVertexPosInBuffer;
	auto v = Vertices;
	v[i+0].Position = v[i+1].Position = v[i+2].Position = v[i+3].Position = p.Position;
	v[i+0].Size =	  v[i+1].Size =		v[i+2].Size =	  v[i+3].Size =		p.Size;
	v[i+0].Opacity =  v[i+1].Opacity =	v[i+2].Opacity =  v[i+3].Opacity =  max(1 - (mCurrentTime - p.SpawnTime) / p.LifeTimeMs, 0);

	v[i+0].TextureCoord = XMFLOAT2(0, 1);
	v[i+1].TextureCoord = XMFLOAT2(0, 0);
	v[i+2].TextureCoord = XMFLOAT2(1, 0);
	v[i+3].TextureCoord = XMFLOAT2(1, 1);
}

uint ParticleSystem::getNextFreeIndex()
{
	uint i = (mPrevFreeIndex + 1) % Desc.NumParticles;
	do
	{
		Particle & p = mParticles[i];
		if(p.SpawnTime + p.LifeTimeMs <= mCurrentTime)
		{
			mPrevFreeIndex = i;
			return i;
		}
		i = (i + 1) % Desc.NumParticles;
	} while(i != mPrevFreeIndex);
	return -1;
}

void ParticleSystem::generateParticle( Particle & p )
{
	XMVECTOR position = XMVectorMultiplyAdd(
		random::direction(), XMVectorReplicate(Desc.MaxInitialPositionDeviation * random::uNorm()),
		XMLoadFloat3(&Position));
	XMStoreFloat3(&p.Position, position);

	XMVECTOR velocity;
	if(Desc.RestrictedMoveDir)
		velocity = XMLoadFloat3(&Desc.InitialMoveDir);
	else
		velocity = random::direction();
	velocity = XMVectorMultiply(velocity, XMVectorReplicate(random::between(Desc.MinInitialSpeed, Desc.MaxInitialSpeed)));
	XMStoreFloat3(&p.Velocity, velocity);

	p.Size = random::between(Desc.MinInitialSize, Desc.MaxInitialSize);

	p.SpawnTime = mCurrentTime;
	p.LifeTimeMs = random::between(Desc.MinLifeTimeMs, Desc.MaxLifeTimeMs);
}

void ParticleSystem::spawnParticles( uint num )
{
	while(num--)
	{
		uint i = getNextFreeIndex();
		if(i == -1)
		{
			LOG_WARNING("Particle system size too small for all particles");
			return;
		}
		generateParticle(mParticles[i]);
	}
}
